
package cn.zlg.common.datadump.condition;

import java.lang.reflect.Method;
import java.sql.Time;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.*;

import javax.servlet.http.HttpServletRequest;

import com.querydsl.core.types.Expression;
import com.querydsl.core.types.Order;
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.Predicate;
import com.querydsl.core.types.dsl.ComparableExpressionBase;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;

public class HttpServletRequestQueryConditionBuilder {

    public static QueryCondtionWrapper build(HttpServletRequest request, QueryCondtionDef def) throws Exception {
        if (def == null) {
            return null;
        }
        QueryFieldCondition[] conditions = def.getAllConditionDef();
        List<QueryFieldConditionSet> conditionSet = def.getConditionSet();
        List<QueryFieldWithValue> list = new ArrayList<>();
        if (conditions != null && conditions.length > 0) {
            Enumeration<String> keys = request.getParameterNames();
            while (keys.hasMoreElements()) {
                String key = keys.nextElement();
                for (QueryFieldCondition f : conditions) {
                    if (key.equals(f.getQueryParamName())) {
                        QueryFieldWithValue qv = new QueryFieldWithValue();
                        qv.setCondition(f);
                        qv.setValue(processQueryFieldCondition(f,request,key));
                        list.add(qv);
                    }
                }
                if(conditionSet != null) {
                    for(QueryFieldConditionSet set : conditionSet) {
                        if(set.containsName(key)) {
                            Object[] result = processQueryFieldConditionSet(set, request, key);
                            if(result[0] != null) {
                                Object value = convertValueToType((Method)result[0], request,key, (QueryFieldCondition) result[1]);
                                System.err.println("result[0]="+result[0]+",value="+value);
                                list.add(new QueryFieldWithValue(set, value));
                            }
                        }
                    }
                }
            }
        }
        Pageable pageable = findPageableParameter(request);
        OrderSpecifier[] orders = findSortParameter(request, def.getAllSortFieldDef());
        Predicate precompiled = null;
        if(def.supportCustomBuild()) {
            precompiled = def.build(list);
        }
        return new QueryCondtionWrapper(precompiled, list, pageable, orders);
    }

    private static Object[] processQueryFieldConditionSet(QueryFieldConditionSet set, HttpServletRequest request, String key) {
        List<QueryFieldCondition> conditions = set.getConditions();
        Object[] result = new Object[2];
        if(conditions != null && conditions.size() > 0) {
            for (QueryFieldCondition condition : conditions) {
                Method method = reflectGetConditionMethod(condition);
                if(result[0] == null) {
                    result[0] = method;
                    result[1] = condition;
                }
            }
        }
        if(set.getConditionSets() != null && set.getConditionSets().size() > 0) {
            for (QueryFieldConditionSet s : set.getConditionSets()) {
                Object[] result2 = processQueryFieldConditionSet(s, request, key);
                if(result[0] == null) {
                    result = result2;
                }
            }
        }

        return result;
    }

    private static Object processQueryFieldCondition(QueryFieldCondition f, HttpServletRequest request, String key) throws ParseException {
        Method conditionMethod = reflectGetConditionMethod(f);
        return convertValueToType(conditionMethod, request, key, f);
    }

    private static Object convertValueToType(Method conditionMethod, HttpServletRequest request, String key, QueryFieldCondition f) throws ParseException {
        if (conditionMethod.getParameterCount() == 1) {
            String[] values = request.getParameterValues(key);
            Object value = null;
            if (values != null) {
                if (values.length == 1 && notInMethod(f.getCondition())) {
                    value = convertValueToType(values[0], f.getRefEntityProperty().getType());
                } else {
                    // 转换成List
                    value = convertValueToTypeList(values, f.getRefEntityProperty().getType());
                }
            }
            return value;
        }
        return null;
    }

    private static Method reflectGetConditionMethod(QueryFieldCondition f) {
        Method conditionMethod = f.getConditionMethod();
        if (conditionMethod == null) {
            conditionMethod = getConditionMethod(f.getRefEntityProperty(), f.getCondition());
            if (conditionMethod.getParameterCount() < 2) {
                f.setConditionMethod(conditionMethod);
            } else {
                throw new RuntimeException(String.format("筛选条件[%s]匹配的方法有%d个参数，不支持超过1个参数的条件方法",
                        f.getQueryParamName(), conditionMethod.getParameterCount()));
            }
        }
        return conditionMethod;
    }

    private static boolean notInMethod(String condition) {
        return !("in".equals(condition) || "notIn".equals(condition));
    }

    private static Method getConditionMethod(ComparableExpressionBase entityProperty, String condition) {
        Method[] methods = entityProperty.getClass().getMethods();
        List<Method> cadidates = new ArrayList<>();
        for (Method m : methods) {
            if (m.getName().equals(condition)) {
                Class<?>[] parameterTypes = m.getParameterTypes();
                boolean hasExpressionParameter = false;
                if (parameterTypes != null && parameterTypes.length > 0) {
                    for (Class type : parameterTypes) {
                        if (Expression.class.isAssignableFrom(type)) {
                            hasExpressionParameter = true;
                            break;
                        }
                    }
                }
                // expression含有多个同名方法，使用不包含Expression参数的方法
                if (!hasExpressionParameter) {
                    // return m;
                    cadidates.add(m);
                }
            }
        }
        return pickupFromCandidates(entityProperty, condition, cadidates);
    }

    private static Method pickupFromCandidates(ComparableExpressionBase entityProperty, String condition,
            List<Method> cadidates) {
        if ("in".equals(condition) || "notIn".equals(condition)) {
            for (Method m : cadidates) {
                Class<?>[] types = m.getParameterTypes();
                if (types != null && types.length > 0) {
                    for (Class c : types) {
                        if (Collection.class.isAssignableFrom(c)) {
                            // 优先选择有collection参数的method
                            return m;
                        }
                    }
                }
            }
        }
        return cadidates.isEmpty() ? null : cadidates.get(0);
    }

    private static OrderSpecifier[] findSortParameter(HttpServletRequest request, SortFieldDef[] allSortFieldDef) {
        String[] sorts = request.getParameterValues("sort");
        List<OrderSpecifier> orders = new ArrayList<>(1);
        if (sorts != null) {
            for (String s : sorts) {
                String[] ss = s.split(",");
                SortFieldDef def = findByParameterName(ss[0], allSortFieldDef);
                if (def != null) {
                    orders.add(new OrderSpecifier(ss.length == 1 ? def.getDefaultOrder() : Order.valueOf(ss[1]),
                            def.getEntityProperty()));
                }
            }
        }
        if(orders.isEmpty()) {
            // 查找默认的排序条件
            for(SortFieldDef def : allSortFieldDef) {
                if(def.isAsDefault()) {
                    orders.add(new OrderSpecifier(def.getDefaultOrder(), def.getEntityProperty()));
                }
            }
        }
        return orders.toArray(new OrderSpecifier[0]);
    }

    private static SortFieldDef findByParameterName(String parameterName, SortFieldDef[] allSortFieldDef) {
        for (SortFieldDef def : allSortFieldDef) {
            if (def.getSortParamName().equals(parameterName)) {
                return def;
            }
        }
        return null;
    }

    private static Pageable findPageableParameter(HttpServletRequest request) {
        Integer page = safeParseInteger(request.getParameter("page"));
        Integer size = safeParseInteger(request.getParameter("size"));
        if (page != null && page >= 0 && size != null && size > 0) {
            // 参数同时存在，且值有意义，返回分页参数
            return PageRequest.of(page, size);
        }
        return null;
    }

    private static Integer safeParseInteger(String page) {
        if (page == null) {
            return null;
        }
        try {
            return Integer.valueOf(page);
        } catch (Exception e) {
        }
        return null;
    }

    public static <T> List<T> extractParameterValuesAndConvert(HttpServletRequest request, String parameter, Class<T> type) throws ParseException {
        String[] parameterValues = request.getParameterValues(parameter);
        if(parameterValues == null) {
            return Collections.EMPTY_LIST;
        }
        List list = new ArrayList();
        for (String v : parameterValues) {
            Object re = convertValueToType(v, type);
            if (re != null) {
                list.add(re);
            }
        }
        return list;
    }

    private static Object convertValueToTypeList(String[] values, Class type) throws ParseException {
        List list = new ArrayList();
        for (String v : values) {
            Object re = convertValueToType(v, type);
            if (re != null) {
                list.add(re);
            }
        }
        return list;
    }

    public static Object convertValueToType(String value, Class toType) throws ParseException {
        if (value == null) {
            // 原生数据的默认值
            if (toType.isPrimitive()) {
                if (toType == int.class)
                    return Integer.valueOf(0);
                else if (toType == long.class)
                    return Long.valueOf(0L);
                else if (toType == byte.class)
                    return Byte.valueOf((byte) 0);
                else if (toType == short.class)
                    return Short.valueOf((short) 0);
                else if (toType == float.class)
                    return Float.valueOf(.0f);
                else if (toType == double.class)
                    return Double.valueOf(.0);
                else if (toType == boolean.class)
                    return Boolean.FALSE;
                else if (toType == char.class)
                    return Character.valueOf(' ');
                throw new RuntimeException("不可能的类型");
            }
            return null;
        }
        if (toType == int.class || toType == Integer.class)
            return Integer.parseInt(value);
        else if (toType == long.class || toType == Long.class)
            return Long.parseLong(value);
        else if (toType == byte.class || toType == Byte.class)
            return Byte.parseByte(value);
        else if (toType == short.class || toType == Short.class)
            return Short.parseShort(value);
        else if (toType == float.class || toType == Float.class)
            return Float.parseFloat(value);
        else if (toType == double.class || toType == Double.class)
            return Double.parseDouble(value);
        else if (toType == boolean.class || toType == Boolean.class)
            return Boolean.parseBoolean(value);
        else if (toType == char.class || toType == Character.class) {
            if (value.length() != 1) {
                throw new IllegalArgumentException("字符型参数值长度必须为1");
            }
            return value.length() == 0 ? ' ' : value.charAt(0);
        } else if (toType == Date.class || toType == java.sql.Date.class || toType == Time.class
                || toType == LocalDate.class || toType == LocalDateTime.class || toType == Calendar.class) {
            try {
                return convertToDate(value, toType, "yyyy-MM-dd HH:mm:ss");
            } catch (Exception e) {
                return convertToDate(value, toType, "yyyy-MM-dd");
            }
        }
        return value;
    }

    /**
     * 将值转换为日期类型。支持如下模式:<br/>
     * 1. 待转换类型支持: java.lang.String, java.lang.Long, java.util.Date, java.sql.Date, java.time.LocalDate,
     * java.time.LocalDateTime<br/>
     * 2. 目标类型支持: java.lang.String, java.lang.Long, java.util.Date, java.sql.Date, java.time.LocalDate,
     * java.time.LocalDateTime<br/>
     * 支持1，2之间每种类型的相互转换。Long类型，将被识别为距1970-1-1日的毫秒时间戳<br/>
     *
     * 如果源数据类型为String，目标类型非String，请务必确保dateFormat和实际类型一致，否则会导致解析错误，抛出异常
     *
     * @param value
     *                        待转换值
     * @param targetClass
     *                        目标类型
     * @param dateFormat
     *                        日期格式
     * @return 转换后的值
     * @throws IllegalArgumentException
     *                                      出现不支持的源类型、目标类型时，抛出此异常
     * @throws ParseException
     *                                      源类型、目标类型仅有一个为String类型，转换失败时出现此异常
     */
    static Object convertToDate(Object value, Class targetClass, String dateFormat) throws ParseException {
        if (value instanceof Date) {
            if (targetClass == String.class) {
                return new SimpleDateFormat(dateFormat).format((Date) value);
            } else if (targetClass == long.class || targetClass == Long.class) {
                return ((Date) value).getTime();
            } else if (targetClass == Date.class) {
                return value;
            } else if (targetClass == LocalDate.class) {
                Calendar c = Calendar.getInstance();
                c.setTime(((Date) value));
                return LocalDate.of(c.get(Calendar.YEAR), c.get(Calendar.MONTH) + 1, c.get(Calendar.DAY_OF_MONTH));
            } else if (targetClass == LocalDateTime.class) {
                Calendar c = Calendar.getInstance();
                c.setTime(((Date) value));
                return LocalDateTime.of(c.get(Calendar.YEAR), c.get(Calendar.MONTH) + 1, c.get(Calendar.DAY_OF_MONTH),
                        c.get(Calendar.HOUR_OF_DAY), c.get(Calendar.MINUTE), c.get(Calendar.SECOND),
                        c.get(Calendar.MILLISECOND) * 1000);
            }
        } else if (value instanceof java.sql.Date) {
            if (targetClass == String.class) {
                return new SimpleDateFormat(dateFormat).format((java.sql.Date) value);
            } else if (targetClass == long.class || targetClass == Long.class) {
                return ((java.sql.Date) value).getTime();
            } else if (targetClass == Date.class || targetClass == java.sql.Date.class) {
                return value;
            } else if (targetClass == LocalDate.class) {
                Calendar c = Calendar.getInstance();
                c.setTime(((Date) value));
                return LocalDate.of(c.get(Calendar.YEAR), c.get(Calendar.MONTH) + 1, c.get(Calendar.DAY_OF_MONTH));
            } else if (targetClass == LocalDateTime.class) {
                Calendar c = Calendar.getInstance();
                c.setTime(((Date) value));
                return LocalDateTime.of(c.get(Calendar.YEAR), c.get(Calendar.MONTH) + 1, c.get(Calendar.DAY_OF_MONTH),
                        c.get(Calendar.HOUR_OF_DAY), c.get(Calendar.MINUTE), c.get(Calendar.SECOND),
                        c.get(Calendar.MILLISECOND) * 1000);
            }
        } else if (value instanceof java.sql.Timestamp) {
            if (targetClass == String.class) {
                return new SimpleDateFormat(dateFormat).format((java.sql.Timestamp) value);
            } else if (targetClass == long.class || targetClass == Long.class) {
                return ((java.sql.Timestamp) value).getTime();
            } else if (targetClass == Date.class || targetClass == java.sql.Date.class) {
                return value;
            } else if (targetClass == LocalDate.class) {
                Calendar c = Calendar.getInstance();
                c.setTime(((Date) value));
                return LocalDate.of(c.get(Calendar.YEAR), c.get(Calendar.MONTH) + 1, c.get(Calendar.DAY_OF_MONTH));
            } else if (targetClass == LocalDateTime.class) {
                Calendar c = Calendar.getInstance();
                c.setTime(((java.sql.Timestamp) value));
                return LocalDateTime.of(c.get(Calendar.YEAR), c.get(Calendar.MONTH) + 1, c.get(Calendar.DAY_OF_MONTH),
                        c.get(Calendar.HOUR_OF_DAY), c.get(Calendar.MINUTE), c.get(Calendar.SECOND),
                        c.get(Calendar.MILLISECOND) * 1000);
            }
        } else if (value instanceof LocalDate) {
            if (targetClass == String.class) {
                return ((LocalDate) value).format(DateTimeFormatter.ofPattern(dateFormat));
            } else if (targetClass == long.class || targetClass == Long.class) {
                return ((LocalDate) value).toEpochDay() * 86400L * 1000;
            } else if (targetClass == LocalDate.class) {
                return value;
            } else if (targetClass == Date.class) {
                LocalDate date = (LocalDate) value;
                return new Date(date.toEpochDay() * 86400L * 1000);
            } else if (targetClass == java.sql.Date.class) {
                LocalDate date = (LocalDate) value;
                return new java.sql.Date(date.toEpochDay() * 86400L * 1000);
            } else if (targetClass == LocalDateTime.class) {
                LocalDate date = (LocalDate) value;
                return date.atTime(0, 0);
            }
        } else if (value instanceof LocalDateTime) {
            if (targetClass == String.class) {
                return ((LocalDateTime) value).format(DateTimeFormatter.ofPattern(dateFormat));
            } else if (targetClass == long.class || targetClass == Long.class) {
                return ((LocalDateTime) value).toEpochSecond(ZoneOffset.UTC) * 1000
                        + ((LocalDateTime) value).getNano() / 1000;
            } else if (targetClass == Date.class) {
                LocalDateTime date = (LocalDateTime) value;
                return new Date(date.toEpochSecond(ZoneOffset.UTC) * 1000 + ((LocalDateTime) value).getNano() / 1000);
            } else if (targetClass == java.sql.Date.class) {
                LocalDateTime date = (LocalDateTime) value;
                return new java.sql.Date(
                        date.toEpochSecond(ZoneOffset.UTC) * 1000 + ((LocalDateTime) value).getNano() / 1000);
            } else if (targetClass == LocalDateTime.class) {
                return value;
            } else if (targetClass == LocalDate.class) {
                return ((LocalDateTime) value).toLocalDate();
            }
        } else if (value.getClass() == String.class) {
            if (targetClass == String.class) {
                return value;
            } else if (targetClass == long.class || targetClass == Long.class) {
                return new SimpleDateFormat(dateFormat).parse(value.toString()).getTime();
            } else if (targetClass == Date.class) {
                return new SimpleDateFormat(dateFormat).parse(value.toString());
            } else if (targetClass == java.sql.Date.class) {
                return new java.sql.Date(new SimpleDateFormat(dateFormat).parse(value.toString()).getTime());
            } else if (targetClass == LocalDate.class) {
                return LocalDate.parse(value.toString(), DateTimeFormatter.ofPattern(dateFormat));
            } else if (targetClass == LocalDateTime.class) {
                return LocalDateTime.parse(value.toString(), DateTimeFormatter.ofPattern(dateFormat));
            }
        } else if (value.getClass() == Long.class || value.getClass() == long.class) {
            if (targetClass == String.class) {
                return new SimpleDateFormat(dateFormat).format(new Date((long) value));
            } else if (targetClass == long.class || targetClass == Long.class) {
                return value;
            } else if (targetClass == Date.class) {
                return new Date((long) value);
            } else if (targetClass == java.sql.Date.class) {
                return new java.sql.Date((long) value);
            } else if (targetClass == LocalDate.class) {
                return LocalDate.ofEpochDay(((long) value) / 86400000L);
            } else if (targetClass == LocalDateTime.class) {
                return LocalDateTime.ofEpochSecond(((long) value) / 86400000L, (int) (((long) value) % 86400L) * 1000,
                        ZoneOffset.UTC);
            }
        }
        throw new IllegalArgumentException(String.format("日期:无法将%s[%s],转换为目标类型%s", value.getClass().getName(),
                value.toString(), targetClass.getName()));
    }

}
